// ------------------------------------------------------------------------
// 版权信息
// 版权归重庆虫儿飞科技有限公司所有。
// 所有权利保留。
// 官方网站：https://netokit.com
// 许可证信息
// Neto.Kit 项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// 
// 使用条款
// 使用本代码应遵守相关法律法规和许可证的要求。
// 
// 免责声明
// 对于因使用本代码而产生的任何直接、间接、偶然、特殊或后果性损害，我们不承担任何责任。
// 
// 其他重要信息
// Neto.Kit 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。
// 有关 Neto.Kit 项目的其他详细信息，请参阅位于源代码树根目录中的 COPYRIGHT 和 DISCLAIMER 文件。
// 
// 更多信息
// 请访问 https://netokit.com 获取更多关于 Neto.Kit 项目的许可证和版权信息。
// ------------------------------------------------------------------------

#region

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Parameters;
using System.Security.Cryptography;

#endregion

namespace Neto.Encry;

public class SM4
{
    /// <summary>
    ///     生成SM4的密钥和初始化向量（IV）。
    /// </summary>
    /// <param name="password">密码。</param>
    /// <param name="keySizeBit">密钥位数（默认为128位）。</param>
    /// <param name="salts">盐值（可选）。</param>
    /// <returns>包含密钥和IV的元组。</returns>
    /// <exception cref="ArgumentNullException">如果密码为空。</exception>
    /// <exception cref="ArgumentException">如果盐值长度不是8的倍数或小于8字节。</exception>
    public static (byte[] Key, byte[] IV) GenerateKeyMaterial(string password, int keySizeBit = 128,
        byte[]? salts = null)
    {
        if (string.IsNullOrEmpty(password)) throw new ArgumentNullException(nameof(password));

        var salt = salts ?? [0x31, 0x33, 0x35, 0x37, 0x32, 0x34, 0x36, 0x38];
        if (salt.Length % 8 != 0 || salt.Length < 8) throw new ArgumentException("加盐数组的长度必须为8的倍数", nameof(salts));

        var iterations = 1000;
        Rfc2898DeriveBytes keyBytesGen = new(password, salt, iterations, HashAlgorithmName.SHA256);

        var keyLen = keySizeBit / 8;
        var key = new byte[keyLen];
        var iv = new byte[16];
        key = keyBytesGen.GetBytes(key.Length);
        iv = keyBytesGen.GetBytes(iv.Length);
        return (Key: key, IV: iv);
    }

    /// <summary>
    ///     使用SM4算法对数据进行CBC模式加密。
    /// </summary>
    /// <param name="data">要加密的数据。</param>
    /// <param name="key">密钥。</param>
    /// <param name="iv">初始化向量（IV）。</param>
    /// <returns>加密后的字节数组。</returns>
    /// <exception cref="ArgumentNullException">如果数据、密钥或IV为空。</exception>
    /// <exception cref="InvalidCipherTextException">如果加密过程中发生错误。</exception>
    public static byte[] CBCEncrypt(byte[] data, byte[] key, byte[] iv)
    {
        if (data == null) throw new ArgumentNullException(nameof(data));
        if (key == null) throw new ArgumentNullException(nameof(key));
        if (iv == null) throw new ArgumentNullException(nameof(iv));

        var engine = new SM4Engine();
        var blockCipher = new CbcBlockCipher(engine);
        var cipher = new PaddedBufferedBlockCipher(blockCipher);
        cipher.Init(true, new ParametersWithIV(new KeyParameter(key), iv));
        try
        {
            return cipher.DoFinal(data);
        }
        catch (InvalidCipherTextException e)
        {
            throw new InvalidOperationException("Failed to encrypt data.", e);
        }
    }

    /// <summary>
    ///     使用SM4算法对数据进行CBC模式加密。
    /// </summary>
    /// <param name="data">要加密的数据。</param>
    /// <param name="key">密钥。</param>
    /// <param name="iv">初始化向量（IV）。</param>
    /// <returns>加密后的字节数组。</returns>
    /// <exception cref="ArgumentNullException">如果数据、密钥或IV为空。</exception>
    /// <exception cref="InvalidCipherTextException">如果加密过程中发生错误。</exception>
    public static string CBCEncrypt(string data, string key, string iv)
    {
        var dataEncrypted = CBCEncrypt(Encoding.UTF8.GetBytes(data), Encoding.UTF8.GetBytes(key),
            Encoding.UTF8.GetBytes(iv));
        return Convert.ToBase64String(dataEncrypted);
    }

    /// <summary>
    ///     使用SM4算法对数据进行CBC模式解密。
    /// </summary>
    /// <param name="dataEncrypted">要解密的数据。</param>
    /// <param name="key">密钥。</param>
    /// <param name="iv">初始化向量（IV）。</param>
    /// <returns>解密后的字节数组。</returns>
    /// <exception cref="ArgumentNullException">如果数据、密钥或IV为空。</exception>
    /// <exception cref="InvalidCipherTextException">如果解密过程中发生错误。</exception>
    public static byte[] CBCDecrypt(byte[] dataEncrypted, byte[] key, byte[] iv)
    {
        if (dataEncrypted == null) throw new ArgumentNullException(nameof(dataEncrypted));
        if (key == null) throw new ArgumentNullException(nameof(key));
        if (iv == null) throw new ArgumentNullException(nameof(iv));

        var engine = new SM4Engine();
        var blockCipher = new CbcBlockCipher(engine);
        var cipher = new PaddedBufferedBlockCipher(blockCipher);
        cipher.Init(false, new ParametersWithIV(new KeyParameter(key), iv));
        try
        {
            return cipher.DoFinal(dataEncrypted);
        }
        catch (InvalidCipherTextException e)
        {
            throw new InvalidOperationException("Failed to decrypt data.", e);
        }
    }

    /// <summary>
    ///     使用SM4算法对数据进行CBC模式解密。
    /// </summary>
    /// <param name="dataEncrypted">要解密的数据。</param>
    /// <param name="key">密钥。</param>
    /// <param name="iv">初始化向量（IV）。</param>
    /// <returns>解密后的字节数组。</returns>
    /// <exception cref="ArgumentNullException">如果数据、密钥或IV为空。</exception>
    /// <exception cref="InvalidCipherTextException">如果解密过程中发生错误。</exception>
    public static string CBCDecrypt(string dataEncrypted, string key, string iv)
    {
        var data = Convert.FromBase64String(dataEncrypted);
        var dataDecrypted = CBCDecrypt(data, Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(iv));
        return Encoding.UTF8.GetString(dataDecrypted);
    }
}